# Copyright (C) 2000-2001 The OpenRPG Project
#
#     openrpg-dev@lists.sourceforge.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# --
#
# File: chatutils.py
# Author: Chris Davis
# Maintainer:
# Version:
#   $Id: chatwnd.py,v 1.177 2007/12/07 20:39:48 digitalxero Exp $
#
# Description: This file contains some of the basic definitions for the chat
# utilities in the orpg project.
#
# History
# 2002-01-20 HeroMan
#   + Added 4 dialog items on toolbar in support of Alias Library Functionallity
#   + Shrunk the text view button to an image
# 2005-04-25 Snowdog
#   + Added simple_html_repair() to post() to fix malformed html in the chat window
#   + Added strip_script_tags() to post() to remove crash point. See chat_util.py

from __future__ import with_statement

import os
import time
import re
import sys
import webbrowser
import traceback

from orpg.orpg_windows import *
from orpg.player_list import WG_LIST

import orpg.tools.rgbhex
import orpg.tools.inputValidator
import orpg.tools.predTextCtrl
from orpg.tools.metamenus import MenuEx

from orpg.orpgCore import open_rpg
from orpg.dirpath import dir_struct
from orpg.tools.settings import settings
from orpg.tools.orpg_log import logger
from orpg.tools.decorators import debugging

from orpg.orpg_version import VERSION

# needed to only send typing/not_typing messages while connected
from orpg.networking.mplay_client import MPLAY_CONNECTED

from orpg.chat import commands, chat_msg, chat_util


NEWCHAT = False
try:
    import wx.webview
    NEWCHAT = True
except:
    pass

def log(text):
    filename = settings.get('GameLogPrefix')
    if filename and filename[0] != commands.ANTI_LOG_CHAR:
        filename = filename + time.strftime('-%Y-%m-%d.html',
                                            time.localtime(time.time()))

        header = ''

        if settings.get('TimeStampGameLog').lower() in ['1', 'on', 'true']:
            timestamp = time.ctime(time.time())
            header = '[%s] : ' % ( timestamp )

        with open(orpg.dirpath.dir_struct["user"] + filename, 'a') as f:
            f.write('<div class="post">%s%s</div>\n' % (header, text))


class chat_html_window(wx.html.HtmlWindow):
    """
    This class displayes the chat information in html?

    Defines:
       __init__(self, parent, id)
       OnLinkClicked(self, linkinfo)
       CalculateAllFonts(self, defaultsize)
       SetDefaultFontAndSize(self, fontname)
    """

    @debugging
    def __init__(self, parent, id):
        wx.html.HtmlWindow.__init__(self, parent, id,
                                    style=wx.SUNKEN_BORDER|
                                    wx.html.HW_SCROLLBAR_AUTO|
                                    wx.NO_FULL_REPAINT_ON_RESIZE)
        self.parent = parent
        self.build_menu()
        self.Bind(wx.EVT_LEFT_UP, self.LeftUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup)

        if "gtk2" in wx.PlatformInfo:
            self.SetStandardFonts()

    @debugging
    def onPopup(self, evt):
        self.PopupMenu(self.menu)

    @debugging
    def LeftUp(self, event):
        event.Skip()
        wx.CallAfter(self.parent.set_chat_text_focus, None)

    @debugging
    def build_menu(self):
        self.menu = wx.Menu()
        item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy")
        self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item)
        self.menu.AppendItem(item)

    @debugging
    def OnM_EditCopy(self, evt):
        wx.TheClipboard.Open()
        wx.TheClipboard.Clear()
        wx.TheClipboard.SetData(wx.TextDataObject(self.SelectionToText()))
        wx.TheClipboard.Close()

    @debugging
    def scroll_down(self):
        maxrange = self.GetScrollRange(wx.VERTICAL)
        pagesize = self.GetScrollPageSize(wx.VERTICAL)
        self.Scroll(-1, maxrange-pagesize)

    @debugging
    def mouse_wheel(self, event):
        amt = event.GetWheelRotation()
        units = amt/(-(event.GetWheelDelta()))
        self.ScrollLines(units*3)

    @debugging
    def Header(self):
        header = ['<html><body bgcolor="',
                  self.parent.bgcolor,
                  '" text="',
                  self.parent.textcolor,
                  '">']
        return ''.join(header)

    @debugging
    def StripHeader(self):
        return self.GetPageSource().replace(self.Header(), '')

    @debugging
    def GetPageSource(self):
        return self.GetParser().GetSource()

    @debugging
    def OnLinkClicked(self, linkinfo):
        href = linkinfo.GetHref()
        wb = webbrowser.get()
        wb.open(href)

    @debugging
    def CalculateAllFonts(self, defaultsize):
        return [int(defaultsize * 0.4),
                int(defaultsize * 0.7),
                int(defaultsize),
                int(defaultsize * 1.3),
                int(defaultsize * 1.7),
                int(defaultsize * 2),
                int(defaultsize * 2.5)]

    @debugging
    def SetDefaultFontAndSize(self, fontname, fontsize):
        """
        Set 'fontname' to the default chat font.
        Returns current font settings in a (fontname, fontsize) tuple.
        """
        self.SetFonts(fontname, "", self.CalculateAllFonts(int(fontsize)))
        return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize())


if NEWCHAT:
    class ChatHtmlWindow(wx.webview.WebView):
        @debugging
        def __init__(self, parent, id):
            wx.webview.WebView.__init__(self, parent, id)

            self.parent = parent

            self.__font = wx.Font(10, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL,
                                  wx.FONTWEIGHT_NORMAL, faceName='Ariel')

            self.build_menu()
            self.Bind(wx.EVT_LEFT_UP, self.LeftUp)
            self.Bind(wx.EVT_RIGHT_DOWN, self.onPopup)
            self.Bind(wx.webview.EVT_WEBVIEW_BEFORE_LOAD, self.OnLinkClicked)

        #Wrapers so I dont have to add special Code
        @debugging
        def SetPage(self, htmlstring):
            self.SetPageSource(htmlstring)

        @debugging
        def AppendToPage(self, htmlstring):
            self.SetPageSource(self.GetPageSource() + htmlstring)

        @debugging
        def GetFont(self):
            return self.__font

        @debugging
        def CalculateAllFonts(self, defaultsize):
            return

        @debugging
        def SetDefaultFontAndSize(self, fontname, fontsize):
            self.__font = wx.Font(int(fontsize), wx.FONTFAMILY_ROMAN,
                                  wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL,
                                  faceName=fontname)
            try:
                self.SetPageSource(self.Header() + self.StripHeader())
            except Exception, e:
                print e
            return (self.GetFont().GetFaceName(), self.GetFont().GetPointSize())

        #Events
        @debugging
        def OnLinkClicked(self, linkinfo):
            href = linkinfo.GetHref()
            wb = webbrowser.get()
            wb.open(href)

        @debugging
        def onPopup(self, evt):
            self.PopupMenu(self.menu)

        @debugging
        def LeftUp(self, event):
            event.Skip()
            wx.CallAfter(self.parent.set_chat_text_focus, None)

        @debugging
        def OnM_EditCopy(self, evt):
            self.Copy()

        #Cutom Methods
        @debugging
        def Header(self):
            header = ['<html><head><style>body {font-size: ',
                      str(self.GetFont().GetPointSize()),
                      'px; font-family: ',
                      self.GetFont().GetFaceName(),
                      '; color: ',
                      self.parent.textcolor,
                      '; background-color: ',
                      self.parent.bgcolor,
                      '; margin: 0; padding: 0 0; height: 100%;}'
                      '</style></head><body>']
            return ''.join(header)

        @debugging
        def StripHeader(self):
            tmp = self.GetPageSource().split('<BODY>')
            if tmp[-1].find('<body>') > -1:
                tmp = tmp[-1].split('<body>')

            return tmp[-1]

        @debugging
        def build_menu(self):
            self.menu = wx.Menu()
            item = wx.MenuItem(self.menu, wx.ID_ANY, "Copy", "Copy")
            self.Bind(wx.EVT_MENU, self.OnM_EditCopy, item)
            self.menu.AppendItem(item)

        @debugging
        def scroll_down(self):
            maxrange = self.GetScrollRange(wx.VERTICAL)
            pagesize = self.GetScrollPageSize(wx.VERTICAL)
            self.Scroll(-1, maxrange-pagesize)

        @debugging
        def mouse_wheel(self, event):
            amt = event.GetWheelRotation()
            units = amt/(-(event.GetWheelDelta()))
            self.ScrollLines(units*3)

    chat_html_window = ChatHtmlWindow

#########################
#chat frame window
#########################
# These are kinda global...and static..and should be located somewhere else
# then the middle of a file between two classes.

###################
# Tab Types
###################
MAIN_TAB = wx.NewId()
WHISPER_TAB = wx.NewId()
GROUP_TAB = wx.NewId()
NULL_TAB = wx.NewId()

class chat_notebook(orpgTabberWnd):
    """
    This class defines the tabbed 'notebook' that holds multiple chatpanels.
    It's the widget attached to the main application frame.

    Inherits:  wxNotebook

    Defines:
        create_private_tab(self, playerid)
        get_tab_index(self, chatpanel)
        destroy_private_tab(self, chatpanel)
        OnPageChanged(self, event)
        set_default_font(self, font, fontsize)
    """
    @debugging
    def __init__(self, parent, size):
        orpgTabberWnd.__init__(self, parent, True, size=size, style=FNB.FNB_DROPDOWN_TABS_LIST|FNB.FNB_NO_NAV_BUTTONS|FNB.FNB_MOUSE_MIDDLE_CLOSES_TABS)

        self.whisper_tabs = []
        self.group_tabs = []
        self.null_tabs = []
        self.il = wx.ImageList(16, 16)
        bmp = wx.Bitmap(orpg.dirpath.dir_struct["icon"]+'player.gif')
        self.il.Add(bmp)
        bmp = wx.Bitmap(orpg.dirpath.dir_struct["icon"]+'clear.gif')
        self.il.Add(bmp)
        self.SetImageList(self.il)

        # Create "main" chatpanel tab, undeletable, connected to 'public' room.
        self.MainChatPanel = chat_panel(self, -1, MAIN_TAB, 'all')
        self.AddPage(self.MainChatPanel, "Main Room")
        self.SetPageImage(0, 1)
        self.chat_timer = wx.Timer(self, wx.NewId())
        self.Bind(wx.EVT_TIMER, self.MainChatPanel.typingTimerFunc)
        self.chat_timer.Start(1000)

        # Hook up event handler for flipping tabs
        self.Bind(FNB.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.onPageChanged)
        self.Bind(FNB.EVT_FLATNOTEBOOK_PAGE_CHANGING, self.onPageChanging)
        self.Bind(FNB.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.onCloseTab)

        # html font/fontsize is global to all the notebook tabs.
        self.font, self.fontsize = self.MainChatPanel.chatwnd\
                            .SetDefaultFontAndSize(settings.get('defaultfont'),
                                  settings.get('defaultfontsize'))
        self.GMChatPanel = None
        if settings.get("GMWhisperTab") == '1':
            self.create_gm_tab()

        self.SetSelection(0)

    @debugging
    def get_tab_index(self, chatpanel):
        """
        Return the index of a chatpanel in the wxNotebook.
        """
        for i in xrange(self.GetPageCount()):
            if (self.GetPage(i) == chatpanel):
                return i

    @debugging
    def create_gm_tab(self):
        if self.GMChatPanel == None:
            self.GMChatPanel = chat_panel(self, -1, MAIN_TAB, 'gm')
            self.AddPage(self.GMChatPanel, "GM", False)
            self.SetPageImage(self.GetPageCount()-1, 1)
            self.GMChatPanel.chatwnd.SetDefaultFontAndSize(self.font,
                                                           self.fontsize)

    @debugging
    def create_whisper_tab(self, playerid):
        """
        Add a new chatpanel directly connected to integer 'playerid' via
        whispering.
        """
        if self.MainChatPanel.session.get_player_by_player_id(playerid) != -1:
            playername = chat_util.strip_html(
                self.MainChatPanel.session.get_player_by_player_id(playerid)[0])

            private_tab = chat_panel(self, -1, WHISPER_TAB, playerid)
            self.AddPage(private_tab, playername, False)
            private_tab.chatwnd.SetDefaultFontAndSize(self.font, self.fontsize)
            self.whisper_tabs.append(private_tab)
            self.newMsg(self.GetPageCount()-1)
            self.AliasLib = open_rpg.get_component('alias')
            wx.CallAfter(self.AliasLib.RefreshAliases)

            return private_tab

        raise ValueError("Player does not Exist")

    @debugging
    def create_group_tab(self, group_name):
        """
        Add a new group whisper tab
        """
        private_tab = chat_panel(self, -1, GROUP_TAB, group_name)
        self.AddPage(private_tab, group_name, False)
        private_tab.chatwnd.SetDefaultFontAndSize(self.font, self.fontsize)
        self.group_tabs.append(private_tab)
        self.newMsg(self.GetPageCount()-1)
        self.AliasLib = open_rpg.get_component('alias')
        wx.CallAfter(self.AliasLib.RefreshAliases)

        return private_tab

    @debugging
    def create_null_tab(self, tab_name):
        """
        Add a new null tab that does not sent any messages you type in it
        """
        private_tab = chat_panel(self, -1, NULL_TAB, tab_name)
        self.AddPage(private_tab, tab_name, False)
        private_tab.chatwnd.SetDefaultFontAndSize(self.font, self.fontsize)
        self.null_tabs.append(private_tab)
        self.newMsg(self.GetPageCount()-1)
        self.AliasLib = open_rpg.get_component('alias')
        wx.CallAfter(self.AliasLib.RefreshAliases)

        return private_tab

    @debugging
    def onCloseTab(self, evt):
        try:
            tabid = evt.GetSelection()
        except:
            tabid = self.GetSelection()

        logger.debug(tabid, True)

        if self.GetPageText(tabid) == 'Main Room':
            #send no close error to chat
            evt.Veto()
            return

        if self.GetPageText(tabid) == 'GM':
            try:
                msg = "Are You Sure You Want To Close This Page?"
                dlg = wx.MessageDialog(self, msg, "NotebookCtrl Question",
                                       wx.YES_NO|wx.NO_DEFAULT|wx.ICON_QUESTION)

                if wx.Platform != '__WXMAC__':
                    dlg.SetFont(wx.Font(8, wx.NORMAL, wx.NORMAL, wx.NORMAL, False))

                if dlg.ShowModal() in [wx.ID_NO]:
                    evt.Veto()
                    return

                self.GMChatPanel = None
                settings.set("GMWhisperTab", "0")
            finally:
                dlg.Destroy()

        panel = self.GetPage(tabid)
        if panel in self.whisper_tabs:
            self.whisper_tabs.remove(panel)
        elif panel in self.group_tabs:
            self.group_tabs.remove(panel)
        elif panel in self.null_tabs:
            self.null_tabs.remove(panel)

    @debugging
    def newMsg(self, tabid):
        if tabid != self.GetSelection():
            self.SetPageImage(tabid, 0)

    @debugging
    def onPageChanging(self, event):
        """
        When private chattabs are selected, set the bitmap back to 'normal'.
        """
        event.Skip()

    @debugging
    def onPageChanged(self, event):
        """
        When private chattabs are selected, set the bitmap back to 'normal'.
        """
        selected_idx = event.GetSelection()
        self.SetPageImage(selected_idx, 1)
        page = self.GetPage(selected_idx)
        event.Skip()


class chat_panel(wx.Panel):
    """
    This class defines and builds the Chat Frame for OpenRPG

    Inherits: wxPanel

    Defines:
       __init__((self, parent, id, openrpg, sendtarget)
       build_ctrls(self)
       on_buffer_size(self,evt)
       set_colors(self)
       set_buffersize(self)
       set_chat_text(self,txt)
       OnChar(self,event)
       on_chat_save(self,evt)
       on_text_color(self,event)
       colorize(self, color, text)
       on_text_format(self,event)
       OnSize(self,event)
       scroll_down(self)
       InfoPost(self,s)
       Post(self,s="",send=False,myself=False)
       ParsePost(self,s,send=False,myself=False)
       ParseDice(self,s)
       ParseNodes(self,s)
       get_sha_checksum(self)
       get_color(self)
    """

    @debugging
    def __init__(self, parent, id, tab_type, sendtarget):
        """
        This is the initialization subroutine

        !self : instance of self
        !parent : parent that defines the chatframe
        !id :
        !openrpg :
            !sendtarget:  who gets outbound messages: either 'all' or a playerid
        """
        wx.Panel.__init__(self, parent, id)
        self.session = open_rpg.get_component('session')
        self.activeplugins = open_rpg.get_component('plugins')

        self.parent = parent

        # who receives outbound messages, either "all" or "playerid" string
        self.sendtarget = sendtarget
        self.type = tab_type
        self.sound_player = open_rpg.get_component('sound')

        # create die roller manager
        self.DiceManager = open_rpg.get_component('DiceManager')

        # create rpghex tool
        self.r_h = orpg.tools.rgbhex.RGBHex()
        self.h = 0
        self.set_colors()
        self.version = VERSION
        self.histidx = -1
        self.temptext = ""
        self.history = []
        self.storedata = []
        self.parsed=0

        #chat commands
        self.lockscroll = False
        self.chat_cmds = commands.chat_commands(self)
        self.html_strip = chat_util.strip_html

        #Alias Lib stuff
        self.defaultAliasName = 'Use Real Name'
        self.defaultFilterName = 'No Filter'
        self.advancedFilter = False
        self.lastSend = 0
        self.lastPress = 0
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.build_ctrls()

        #openrpg dir
        self.root_dir = orpg.dirpath.dir_struct["home"]

        # html font/fontsize is global to all the notebook tabs.
        StartupFont = settings.get("defaultfont")
        StartupFontSize = settings.get("defaultfontsize")

        if(StartupFont != "") and (StartupFontSize != ""):
            try:
                self.set_default_font(StartupFont, int(StartupFontSize))
            except:
                pass

        self.font = self.chatwnd.GetFont().GetFaceName()
        self.fontsize = self.chatwnd.GetFont().GetPointSize()
        self.scroll_down()

    @debugging
    def set_default_font(self, fontname=None, fontsize=None):
        """
        Set all chatpanels to new default fontname/fontsize. Returns current
        font settings in a (fontname, fontsize) tuple.
        """
        if (fontname is not None):
            newfont = fontname
        else:
            newfont = self.font

        if (fontsize is not None):
            newfontsize = int(fontsize)
        else:
            newfontsize = int(self.fontsize)

        self.chatwnd.SetDefaultFontAndSize(newfont, newfontsize)
        msg = ["Font is now",
               newfont,
               "point size",
               str(newfontsize)]
        self.InfoPost(' '.join(msg))
        self.font = newfont
        self.fontsize = newfontsize

        return (self.font, self.fontsize)

    @debugging
    def build_menu(self):
        top_frame = open_rpg.get_component('frame')
        menu = wx.Menu()
        item = wx.MenuItem(menu, wx.ID_ANY, "&Background color",
                           "Background color")
        top_frame.Bind(wx.EVT_MENU, self.OnMB_BackgroundColor, item)
        menu.AppendItem(item)

        item = wx.MenuItem(menu, wx.ID_ANY, "&Text color", "Text color")
        top_frame.Bind(wx.EVT_MENU, self.OnMB_TextColor, item)
        menu.AppendItem(item)

        menu.AppendSeparator()

        item = wx.MenuItem(menu, wx.ID_ANY, "&Chat Focus\tCtrl-H",
                           "Chat Focus")
        self.setChatFocusMenu = item
        top_frame.Bind(wx.EVT_MENU, self.set_chat_text_focus, item)
        menu.AppendItem(item)

        menu.AppendSeparator()

        item = wx.MenuItem(menu, wx.ID_ANY, "Toggle &Scroll Lock",
                           "Toggle Scroll Lock")
        top_frame.Bind(wx.EVT_MENU, self.lock_scroll, item)
        menu.AppendItem(item)

        item = wx.MenuItem(menu, wx.ID_ANY, "Save Chat &Log",
                           "Save Chat Log")
        top_frame.Bind(wx.EVT_MENU, self.on_chat_save, item)
        menu.AppendItem(item)

        item = wx.MenuItem(menu, wx.ID_ANY, "Text &View", "Text View")
        top_frame.Bind(wx.EVT_MENU, self.pop_textpop, item)
        menu.AppendItem(item)

        item = wx.MenuItem(menu, wx.ID_ANY, "Forward Tab\tCtrl+Tab",
                           "Swap Tabs")
        top_frame.Bind(wx.EVT_MENU, self.forward_tabs, item)
        menu.AppendItem(item)

        item = wx.MenuItem(menu, wx.ID_ANY, "Forward Tab\tCtrl+Shift+Tab",
                           "Swap Tabs")
        top_frame.Bind(wx.EVT_MENU, self.back_tabs, item)
        menu.AppendItem(item)

        menu.AppendSeparator()

        settingmenu = wx.Menu()
        wndmenu = wx.Menu()
        tabmenu = wx.Menu()
        toolmenu = wx.Menu()

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Show Images","Show Images",
                           wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_ShowImages, item)
        wndmenu.AppendItem(item)
        if settings.get("Show_Images_In_Chat") == '1':
            item.Check(True)

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Strip HTML", "Strip HTML",
                           wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_StripHTML, item)
        wndmenu.AppendItem(item)
        if settings.get("striphtml") == '1':
            item.Check(True)

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Chat Time Index",
                           "Chat Time Index", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_ChatTimeIndex, item)
        wndmenu.AppendItem(item)
        if settings.get("Chat_Time_Indexing") == '1':
            item.Check(True)

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Chat Auto Complete",
                           "Chat Auto Complete", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_ChatAutoComplete, item)
        wndmenu.AppendItem(item)
        if settings.get("SuppressChatAutoComplete") == '0':
            item.Check(True)

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Show ID in Chat",
                           "Show ID in Chat", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_ShowIDinChat, item)
        wndmenu.AppendItem(item)
        if settings.get("ShowIDInChat") == '1':
            item.Check(True)

        item = wx.MenuItem(wndmenu, wx.ID_ANY, "Log Time Index",
                           "Log Time Index", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_LogTimeIndex, item)
        wndmenu.AppendItem(item)
        if settings.get("TimeStampGameLog") == '1':
            item.Check(True)

        settingmenu.AppendMenu(wx.ID_ANY, 'Chat Window', wndmenu)

        item = wx.MenuItem(tabmenu, wx.ID_ANY, "Tabbed Whispers",
                           "Tabbed Whispers", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_TabbedWhispers, item)
        tabmenu.AppendItem(item)
        if settings.get("tabbedwhispers") == '1':
            item.Check(True)

        item = wx.MenuItem(tabmenu, wx.ID_ANY, "GM Tab", "GM Tab",
                           wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_GMTab, item)
        tabmenu.AppendItem(item)
        if settings.get("GMWhisperTab") == '1':
            item.Check(True)

        item = wx.MenuItem(tabmenu, wx.ID_ANY, "Group Whisper Tabs",
                           "Group Whisper Tabs", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_GroupWhisperTabs, item)
        tabmenu.AppendItem(item)
        if settings.get("GroupWhisperTab") == '1':
            item.Check(True)

        settingmenu.AppendMenu(wx.ID_ANY, 'Chat Tabs', tabmenu)

        item = wx.MenuItem(toolmenu, wx.ID_ANY, "Dice Bar", "Dice Bar",
                           wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_DiceBar, item)
        toolmenu.AppendItem(item)
        if settings.get("DiceButtons_On") == '1':
            item.Check(True)

        item = wx.MenuItem(toolmenu, wx.ID_ANY, "Format Buttons",
                           "Format Buttons", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_FormatButtons, item)
        toolmenu.AppendItem(item)
        if settings.get("FormattingButtons_On") == '1':
            item.Check(True)

        item = wx.MenuItem(toolmenu, wx.ID_ANY, "Alias Tool",
                           "Alias Tool", wx.ITEM_CHECK)
        top_frame.Bind(wx.EVT_MENU, self.OnMB_AliasTool, item)
        toolmenu.AppendItem(item)
        if settings.get("AliasTool_On") == '1':
            item.Check(True)

        settingmenu.AppendMenu(wx.ID_ANY, 'Chat Tool Bars', toolmenu)

        menu.AppendMenu(wx.ID_ANY, 'Chat Settings', settingmenu)
        top_frame.mainmenu.Insert(2, menu, '&Chat')

    # Settings Menu Events
    @debugging
    def OnMB_ShowImages(self, event):
        if event.IsChecked():
            settings.set("Show_Images_In_Chat", '1')
        else:
            settings.set("Show_Images_In_Chat", '0')

    @debugging
    def OnMB_StripHTML(self, event):
        if event.IsChecked():
            settings.set("striphtml", '1')
        else:
            settings.set("striphtml", '0')

    @debugging
    def OnMB_ChatTimeIndex(self, event):
        if event.IsChecked():
            settings.set("Chat_Time_Indexing", '1')
        else:
            settings.set("Chat_Time_Indexing", '0')

    @debugging
    def OnMB_ChatAutoComplete(self, event):
        if event.IsChecked():
            settings.set("SuppressChatAutoComplete", '0')
        else:
            settings.set("SuppressChatAutoComplete", '1')

    @debugging
    def OnMB_ShowIDinChat(self, event):
        if event.IsChecked():
            settings.set("ShowIDInChat", '1')
        else:
            settings.set("ShowIDInChat", '0')

    @debugging
    def OnMB_LogTimeIndex(self, event):
        if event.IsChecked():
            settings.set("TimeStampGameLog", '1')
        else:
            settings.set("TimeStampGameLog", '0')

    @debugging
    def OnMB_TabbedWhispers(self, event):
        if event.IsChecked():
            settings.set("tabbedwhispers", '1')
        else:
            settings.set("tabbedwhispers", '0')

    @debugging
    def OnMB_GMTab(self, event):
        if event.IsChecked():
            settings.set("GMWhisperTab", '1')
            self.parent.create_gm_tab()
        else:
            settings.set("GMWhisperTab", '0')

    @debugging
    def OnMB_GroupWhisperTabs(self, event):
        if event.IsChecked():
            settings.set("GroupWhisperTab", '1')
        else:
            settings.set("GroupWhisperTab", '0')

    @debugging
    def OnMB_DiceBar(self, event):
        act = '0'
        if event.IsChecked():
            settings.set("DiceButtons_On", '1')
            act = '1'
        else:
            settings.set("DiceButtons_On", '0')
        self.toggle_dice(act)

        try:
            self.parent.GMChatPanel.toggle_dice(act)
        except:
            pass

        for panel in self.parent.whisper_tabs:
            panel.toggle_dice(act)
        for panel in self.parent.group_tabs:
            panel.toggle_dice(act)
        for panel in self.parent.null_tabs:
            panel.toggle_dice(act)

    @debugging
    def OnMB_FormatButtons(self, event):
        act = '0'
        if event.IsChecked():
            settings.set("FormattingButtons_On", '1')
            act = '1'
        else:
            settings.set("FormattingButtons_On", '0')
        self.toggle_formating(act)
        try:
            self.parent.GMChatPanel.toggle_formating(act)
        except:
            pass
        for panel in self.parent.whisper_tabs:
            panel.toggle_formating(act)
        for panel in self.parent.group_tabs:
            panel.toggle_formating(act)
        for panel in self.parent.null_tabs:
            panel.toggle_formating(act)

    @debugging
    def OnMB_AliasTool(self, event):
        act = '0'
        if event.IsChecked():
            settings.set("AliasTool_On", '1')
            act = '1'
        else:
            settings.set("AliasTool_On", '0')
        self.toggle_alias(act)
        try:
            self.parent.GMChatPanel.toggle_alias(act)
        except:
            pass
        for panel in self.parent.whisper_tabs:
            panel.toggle_alias(act)
        for panel in self.parent.group_tabs:
            panel.toggle_alias(act)
        for panel in self.parent.null_tabs:
            panel.toggle_alias(act)

    @debugging
    def OnMB_BackgroundColor(self, event):
        top_frame = open_rpg.get_component('frame')
        hexcolor = self.get_color()

        if hexcolor != None:
            self.bgcolor = hexcolor
            settings.set('bgcolor', hexcolor)
            self.chatwnd.SetPage(self.ResetPage())

            if settings.get('ColorTree') == '1':
                top_frame.tree.SetBackgroundColour(settings.get('bgcolor'))
                top_frame.tree.Refresh()
                top_frame.players.SetBackgroundColour(settings.get('bgcolor'))
                top_frame.players.Refresh()
            else:
                top_frame.tree.SetBackgroundColour('white')
                top_frame.tree.SetForegroundColour('black')
                top_frame.tree.Refresh()
                top_frame.players.SetBackgroundColour('white')
                top_frame.players.SetForegroundColour('black')
                top_frame.players.Refresh()

        self.chatwnd.scroll_down()

    @debugging
    def OnMB_TextColor(self, event):
        top_frame = open_rpg.get_component('frame')
        hexcolor = self.get_color()

        if hexcolor != None:
            self.textcolor = hexcolor
            settings.set('textcolor', hexcolor)
            self.chatwnd.SetPage(self.ResetPage())

            if settings.get('ColorTree') == '1':
                top_frame.tree.SetForegroundColour(settings.get('textcolor'))
                top_frame.tree.Refresh()
                top_frame.players.SetForegroundColour(settings.get('textcolor'))
                top_frame.players.Refresh()
            else:
                top_frame.tree.SetBackgroundColour('white')
                top_frame.tree.SetForegroundColour('black')
                top_frame.tree.Refresh()
                top_frame.players.SetBackgroundColour('white')
                top_frame.players.SetForegroundColour('black')
                top_frame.players.Refresh()

        self.chatwnd.scroll_down()

    @debugging
    def get_hot_keys(self):
        # dummy menus for hotkeys
        self.build_menu()
        entries = []
        entries.append((wx.ACCEL_CTRL, ord('H'), self.setChatFocusMenu.GetId()))

        return entries

    @debugging
    def forward_tabs(self, evt):
        self.parent.AdvanceSelection()

    @debugging
    def back_tabs(self, evt):
        self.parent.AdvanceSelection(False)


    @debugging
    def build_ctrls(self):
        self.chatwnd = chat_html_window(self,-1)
        self.set_colors()
        wx.CallAfter(self.chatwnd.SetPage, self.chatwnd.Header())

        if (self.sendtarget == "all"):
            msg = ['<b>Welcome to ',
                   '<a href="http://www.openrpg.com">OpenRPG</a> version',
                   self.version,
                   '...  </b>']
            wx.CallAfter(self.Post, self.colorize(self.syscolor,
                                                  ' '.join(msg)))

        self.chattxt = orpg.tools.predTextCtrl.predTextCtrl(self, -1, "",
                                            style=wx.TE_PROCESS_ENTER|
                                            wx.TE_PROCESS_TAB,
                                            keyHook=self.myKeyHook,
                                            validator=None)
        self.build_bar()
        self.basesizer = wx.BoxSizer(wx.VERTICAL)
        self.basesizer.Add(self.chatwnd,1,wx.EXPAND)
        self.basesizer.Add(self.toolbar_sizer, 0, wx.EXPAND)
        self.basesizer.Add(self.chattxt, 0, wx.EXPAND)
        self.SetSizer(self.basesizer)
        self.SetAutoLayout(True)
        self.Fit()
        self.update_color_button(self.mytextcolor)

        #events
        self.Bind(wx.EVT_BUTTON, self.on_text_format, self.boldButton)
        self.Bind(wx.EVT_BUTTON, self.on_text_format, self.italicButton)
        self.Bind(wx.EVT_BUTTON, self.on_text_format, self.underlineButton)
        self.Bind(wx.EVT_BUTTON, self.on_text_color, self.color_button)
        self.Bind(wx.EVT_BUTTON, self.on_chat_save, self.saveButton)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d4Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d6Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d8Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d10Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d12Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d20Button)
        self.Bind(wx.EVT_BUTTON, self.onDieRoll, self.d100Button)

        self.dieIDs = {}
        self.dieIDs[self.d4Button.GetId()] = 'd4'
        self.dieIDs[self.d6Button.GetId()] = 'd6'
        self.dieIDs[self.d8Button.GetId()] = 'd8'
        self.dieIDs[self.d10Button.GetId()] = 'd10'
        self.dieIDs[self.d12Button.GetId()] = 'd12'
        self.dieIDs[self.d20Button.GetId()] = 'd20'
        self.dieIDs[self.d100Button.GetId()] = 'd100'

        self.Bind(wx.EVT_BUTTON, self.pop_textpop, self.textpop_lock)
        self.Bind(wx.EVT_BUTTON, self.lock_scroll, self.scroll_lock)
        self.chattxt.Bind(wx.EVT_MOUSEWHEEL, self.chatwnd.mouse_wheel)
        self.chattxt.Bind(wx.EVT_CHAR, self.chattxt.OnChar)
        self.chattxt.Bind(wx.EVT_TEXT_COPY, self.textCopy)

    @debugging
    def textCopy(self, event):
        if self.chattxt.GetStringSelection() == '':
            self.chatwnd.OnM_EditCopy(None)
        else:
            event.Skip()

    @debugging
    def build_bar(self):
        self.toolbar_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.scroll_lock = None
        self.numDieText = None
        self.dieModText = None

        if settings.get('Toolbar_On') == "1":
            self.build_alias()
            self.build_dice()
            self.build_scroll()
            self.build_text()
            self.toolbar_sizer.Add( self.textpop_lock, 0, wx.EXPAND )
            self.toolbar_sizer.Add(self.scroll_lock,0,wx.EXPAND)
            self.build_formating()
            self.build_colorbutton()
            self.build_misc_controls()

    @debugging
    def build_scroll(self):
        self.scroll_lock = wx.Button(self, wx.ID_ANY, "Scroll ON",
                                      size=wx.Size(80,25))

    @debugging
    def build_alias(self):
        self.aliasList = wx.Choice(self, wx.ID_ANY, size=(100, 25),
                                   choices=[self.defaultAliasName])
        tip = 'Refresh list of aliases from Game Tree'
        self.aliasButton = createMaskedButton(self,
                                              dir_struct["icon"]+'player.gif',
                                              tip, wx.ID_ANY, '#bdbdbd')
        self.aliasList.SetSelection(0)

        self.filterList = wx.Choice(self, wx.ID_ANY, size=(100, 25),
                                    choices=[self.defaultFilterName])
        tip = 'Refresh list of filters from Game Tree'
        self.filterButton = createMaskedButton(self,
                                        dir_struct["icon"]+'add_filter.gif',
                                        tip, wx.ID_ANY, '#bdbdbd')
        self.filterList.SetSelection(0)

        self.toolbar_sizer.Add(self.aliasButton, 0, wx.EXPAND)
        self.toolbar_sizer.Add(self.aliasList,0,wx.EXPAND)
        self.toolbar_sizer.Add(self.filterButton, 0, wx.EXPAND)
        self.toolbar_sizer.Add(self.filterList,0,wx.EXPAND)

        if settings.get('AliasTool_On') == '0':
            self.toggle_alias('0')
        else:
            self.toggle_alias('1')

    @debugging
    def toggle_alias(self, act):
        if act == '0':
            self.toolbar_sizer.Show(self.aliasList, False)
            self.toolbar_sizer.Show(self.filterList, False)
            self.toolbar_sizer.Show(self.aliasButton, False)
            self.toolbar_sizer.Show(self.filterButton, False)
            self.toolbar_sizer.Layout()
        else:
            self.toolbar_sizer.Show(self.aliasList, True)
            self.toolbar_sizer.Show(self.filterList, True)
            self.toolbar_sizer.Show(self.aliasButton, True)
            self.toolbar_sizer.Show(self.filterButton, True)
            self.toolbar_sizer.Layout()

    @debugging
    def build_text(self):
        tip = 'Open Text View Of Chat Session'
        self.textpop_lock = createMaskedButton(self,
                                               dir_struct["icon"]+'note.gif',
                                               tip, wx.ID_ANY, '#bdbdbd')

    @debugging
    def build_dice(self):
        self.numDieText = wx.TextCtrl(self, wx.ID_ANY, "1",
                    size=wx.Size(25, 25),
                    validator=orpg.tools.inputValidator.MathOnlyValidator())
        self.dieModText = wx.TextCtrl(self, wx.ID_ANY, "",
                    size= wx.Size(50, 25),
                    validator=orpg.tools.inputValidator.MathOnlyValidator())

        #Fixed masking for buttons -- Snowdog 8-09
        self.d4Button = createMaskedButton(self, dir_struct["icon"]+'b_d4.gif',
                                           'Roll d4', wx.ID_ANY, wx.WHITE)
        self.d6Button = createMaskedButton(self, dir_struct["icon"]+'b_d6.gif',
                                           'Roll d6', wx.ID_ANY, wx.WHITE)
        self.d8Button = createMaskedButton(self, dir_struct["icon"]+'b_d8.gif',
                                           'Roll d8', wx.ID_ANY, wx.WHITE)
        self.d10Button = createMaskedButton(self,
                                            dir_struct["icon"]+'b_d10.gif',
                                            'Roll d10', wx.ID_ANY, wx.WHITE)
        self.d12Button = createMaskedButton(self,
                                            dir_struct["icon"]+'b_d12.gif',
                                            'Roll d12', wx.ID_ANY, wx.WHITE)
        self.d20Button = createMaskedButton(self,
                                            dir_struct["icon"]+'b_d20.gif',
                                            'Roll d20', wx.ID_ANY, wx.WHITE)
        self.d100Button = createMaskedButton(self,
                                             dir_struct["icon"]+'b_d100.gif',
                                             'Roll d100', wx.ID_ANY, wx.WHITE)

        self.toolbar_sizer.Add(self.numDieText, 0, wx.ALIGN_CENTER|wx.EXPAND)
        self.toolbar_sizer.Add(self.d4Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d6Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d8Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d10Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d12Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d20Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.d100Button, 0 ,wx.EXPAND)
        self.toolbar_sizer.Add(self.dieModText, 0, wx.ALIGN_CENTER, 5)

        if settings.get('DiceButtons_On') == '0':
            self.toggle_dice('0')
        else:
            self.toggle_dice('1')

    @debugging
    def toggle_dice(self, act):
        if act == '0':
            self.toolbar_sizer.Show(self.numDieText, False)
            self.toolbar_sizer.Show(self.d4Button, False)
            self.toolbar_sizer.Show(self.d6Button, False)
            self.toolbar_sizer.Show(self.d8Button, False)
            self.toolbar_sizer.Show(self.d10Button, False)
            self.toolbar_sizer.Show(self.d12Button, False)
            self.toolbar_sizer.Show(self.d20Button, False)
            self.toolbar_sizer.Show(self.d100Button, False)
            self.toolbar_sizer.Show(self.dieModText, False)
            self.toolbar_sizer.Layout()
        else:
            self.toolbar_sizer.Show(self.numDieText, True)
            self.toolbar_sizer.Show(self.d4Button, True)
            self.toolbar_sizer.Show(self.d6Button, True)
            self.toolbar_sizer.Show(self.d8Button, True)
            self.toolbar_sizer.Show(self.d10Button, True)
            self.toolbar_sizer.Show(self.d12Button, True)
            self.toolbar_sizer.Show(self.d20Button, True)
            self.toolbar_sizer.Show(self.d100Button, True)
            self.toolbar_sizer.Show(self.dieModText, True)
            self.toolbar_sizer.Layout()

    @debugging
    def build_formating(self):
        self.boldButton = createMaskedButton(self,
                                             dir_struct["icon"]+'bold.gif',
                                             'Make the selected text Bold',
                                             wx.ID_ANY, '#bdbdbd')
        self.italicButton = createMaskedButton(self,
                                               dir_struct["icon"]+'italic.gif',
                                               'Italicize the selected text',
                                               wx.ID_ANY, '#bdbdbd')
        self.underlineButton = createMaskedButton(self,
                                        dir_struct["icon"]+'underlined.gif',
                                        'Underline the selected text',
                                        wx.ID_ANY, '#bdbdbd')

        self.toolbar_sizer.Add(self.boldButton, 0, wx.EXPAND)
        self.toolbar_sizer.Add(self.italicButton, 0, wx.EXPAND)
        self.toolbar_sizer.Add(self.underlineButton, 0, wx.EXPAND)

        if settings.get('FormattingButtons_On') == '0':
            self.toggle_formating('0')
        else:
            self.toggle_formating('1')

    @debugging
    def toggle_formating(self, act):
        if act == '0':
            self.toolbar_sizer.Show(self.boldButton, False)
            self.toolbar_sizer.Show(self.italicButton, False)
            self.toolbar_sizer.Show(self.underlineButton, False)
            self.toolbar_sizer.Layout()
        else:
            self.toolbar_sizer.Show(self.boldButton, True)
            self.toolbar_sizer.Show(self.italicButton, True)
            self.toolbar_sizer.Show(self.underlineButton, True)
            self.toolbar_sizer.Layout()


    @debugging
    def build_colorbutton(self):
        self.color_button_image = wx.Image(dir_struct["icon"]+'textcolor.gif',
                                        wx.BITMAP_TYPE_GIF).ConvertToBitmap()
        mask = wx.Mask(self.color_button_image, '#bdbdbd')
        #stored so we don't have to keep reloading image
        self.color_button_image.SetMask(mask)
        self.color_button = wx.BitmapButton(self, wx.ID_ANY,
                                            self.color_button_image)
        self.color_button.SetToolTip(wx.ToolTip('Text Color'))

        self.toolbar_sizer.Add(self.color_button, 0, wx.EXPAND)

    def build_misc_controls(self):
        self.saveButton = createMaskedButton(self,
                                             dir_struct["icon"]+'save.bmp',
                                             'Save the chatbuffer', wx.ID_ANY,
                                             '#c0c0c0', wx.BITMAP_TYPE_BMP)
        self.toolbar_sizer.Add(self.saveButton, 0, wx.EXPAND)

    @debugging
    def OnMotion(self, evt):
        contain = self.chatwnd.GetInternalRepresentation()

        if contain:
            sx = sy = 0
            x = y = 0
            (sx,sy) = self.chatwnd.GetViewStart()
            (sx1,sy1) = self.chatwnd.GetScrollPixelsPerUnit()
            sx = sx*sx1
            sy = sy*sy1
            (x,y) = evt.GetPosition()
            lnk = contain.GetLink(sx+x,sy+y)

            if lnk:
                try:
                    link = lnk.GetHref()
                    self.session.set_status_url(link)
                except:
                    pass

        evt.Skip()

    @debugging
    def myKeyHook(self, event):
        """
        This subroutine is registered with predTextCtrl to be run for every
        OnChar event it checks if we need to send a typing message

        self:  duh
        event:  raw KeyEvent from OnChar()
        """
        #only do if we're connected
        if self.session.get_status() == MPLAY_CONNECTED:
            thisPress = time.time()
            if (thisPress - self.lastSend) > 4:
                self.sendTyping(1)
            self.lastPress = thisPress

        if settings.get('SuppressChatAutoComplete') == '1':
            return 1
        else:
            return 0

    #@debugging This is called once a second, dont debug it
    def typingTimerFunc(self, event):
        """
        plugin refresh_counter code
        """
        for plugin_fname in self.activeplugins.keys():
            plugin = self.activeplugins[plugin_fname]
            plugin.refresh_counter()


        if self.lastSend:
            thisTime = time.time()
            if (thisTime - self.lastPress) > 4:
                self.sendTyping(0)

    @debugging
    def sendTyping(self, typing):
        if typing:
            self.lastSend = time.time()
            status_text = settings.get('TypingStatusAlias')

            if status_text == "" or status_text == None:
                status_text = "Typing"

            self.session.set_text_status(status_text)
        else:
            self.lastSend = 0
            status_text = settings.get('IdleStatusAlias')

            if status_text == "" or status_text == None:
                status_text = "Idle"
            self.session.set_text_status(status_text)

    @debugging
    def set_colors(self):
        # chat window backround color
        self.bgcolor = settings.get('bgcolor')

        # chat window normal text color
        self.textcolor = settings.get('textcolor')

        # color of text player types
        self.mytextcolor = settings.get('mytextcolor')

        # color of system warnings
        self.syscolor = settings.get('syscolor')

        # color of system info messages
        self.infocolor = settings.get('infocolor')

        # color of emotes
        self.emotecolor = settings.get('emotecolor')

        # color of whispers
        self.whispercolor = settings.get('whispercolor')

    @debugging
    def set_chat_text(self, txt):
        self.chattxt.SetValue(txt)
        self.chattxt.SetFocus()
        self.chattxt.SetInsertionPointEnd()

    @debugging
    def get_chat_text(self):
        return self.chattxt.GetValue()

    @debugging
    def set_chat_text_focus(self, event):
        wx.CallAfter(self.chattxt.SetFocus)

    @debugging
    def OnChar(self, event):
        s = self.chattxt.GetValue()

        """
        RETURN KEY (no matter if there is text in chattxt)
        This section is run even if there is nothing in the chattxt
        (as opposed to the next wx.WXK_RETURN handler
        """
        if event.GetKeyCode() == wx.WXK_RETURN:
            self.set_colors()
            if self.session.get_status() == MPLAY_CONNECTED:
                self.sendTyping(0)

        macroText=""

        # Append to the existing typed text as needed and make sure the
        # status doesn't change back.
        if len(macroText):
            self.sendTyping(0)
            s = macroText

        if (event.GetKeyCode() == wx.WXK_RETURN and len(s)) or len(macroText):
            self.histidx = -1
            self.temptext = ""
            self.history = [s] + self.history

            if not len(macroText):
                self.chattxt.SetValue("")

            # play sound
            sound_file = settings.get("SendSound")
            if sound_file != '':
                self.sound_player.play(sound_file)

            if s[0] != "/": ## it's not a slash command
                s = self.ParsePost(s, True, True)
            else:
                self.chat_cmds.docmd(s)

        elif event.GetKeyCode() == wx.WXK_UP:
            if self.histidx < len(self.history)-1:
                """
                text that's not in history but also hasn't been sent to chat
                gets stored in self.temptext this way if someone presses the
                up key, they don't lose their current message permanently
                (unless they also press enter at the time)
                """
                if self.histidx is -1:
                    self.temptext = self.chattxt.GetValue()

                self.histidx += 1
                self.chattxt.SetValue(self.history[self.histidx])
                self.chattxt.SetInsertionPointEnd()
            else:
                self.histidx = len(self.history) -1

        elif event.GetKeyCode() == wx.WXK_DOWN:
            """
            histidx of -1 indicates currently viewing text that's not
            in self.history
            """
            if self.histidx > -1:
                self.histidx -= 1
                if self.histidx is -1:
                    self.chattxt.SetValue(self.temptext)
                else:
                    self.chattxt.SetValue(self.history[self.histidx])
                self.chattxt.SetInsertionPointEnd()
            else:
                self.histidx = -1

        elif  event.GetKeyCode() == wx.WXK_TAB:
            if s !="":
                found = 0
                nicks = []
                testnick = ""
                inlength = len(s)

                for getnames in self.session.players.keys():
                    striphtmltag = re.compile ('<[^>]+>*')
                    testnick = striphtmltag.sub ("",
                                            self.session.players[getnames][0])
                    if string.lower(s) == string.lower(testnick[:inlength]):
                        found = found + 1
                        nicks[len(nicks):]=[testnick]

                if found == 0: ## no nick match
                    self.Post(self.colorize(self.syscolor,
                                            " ** No match found"))

                elif found > 1:
                    nickstring = ""
                    nicklist = []

                    for foundnicks in nicks:
                        nickstring = nickstring + foundnicks + ", "
                        nicklist.append(foundnicks)
                    nickstring = nickstring[:-2]
                    self.Post(self.colorize(self.syscolor,
                                " ** Multiple matches found: " + nickstring))

                    # set text to the prefix match between first two matches
                    settext = re.match(''.join(map(lambda x: '(%s?)' % x,
                                                   string.lower(nicklist[0]))),
                                       string.lower(nicklist[1])).group()

                    # run through the rest of the nicks
                    for i in nicklist:
                        settext = re.match(''.join(map(lambda x: '(%s?)' % x,
                                                       string.lower(i))),
                                           string.lower(settext)).group()

                    if settext:
                        self.chattxt.SetValue(settext)
                        self.chattxt.SetInsertionPointEnd()

                else:
                    settext = nicks[0] + ": "
                    self.chattxt.SetValue(settext)
                    self.chattxt.SetInsertionPointEnd()
            else:
                self.Post(self.colorize(self.syscolor,
                                        " ** That's the Tab key, Dave"))

        elif event.GetKeyCode() in (wx.WXK_PRIOR, wx.WXK_PAGEUP):
            self.chatwnd.ScrollPages(-1)

            if not self.lockscroll:
                self.lock_scroll(0)

        elif event.GetKeyCode() in (wx.WXK_NEXT, wx.WXK_PAGEDOWN):
            if not self.lockscroll:
                self.lock_scroll(0)

            if (self.chatwnd.GetScrollRange(1)-
                self.chatwnd.GetScrollPos(1)-
                self.chatwnd.GetScrollThumb(1) < 30) and self.lockscroll:
                self.lock_scroll(0)

            self.chatwnd.ScrollPages(1)

        elif event.GetKeyCode() == wx.WXK_END:
            if self.lockscroll:
                self.lock_scroll(0)
                self.Post()
            event.Skip()

        else:
            event.Skip()

    @debugging
    def onDieRoll(self, evt):
        """
        Roll the dice based on the button pressed and the die modifiers
        entered, if any.
        """
        # Get any die modifiers if they have been entered
        numDie = self.numDieText.GetValue()
        dieMod = self.dieModText.GetValue()
        dieText = numDie

        # Now, apply and roll die mods based on the button that was pressed
        id = evt.GetId()
        if self.dieIDs.has_key(id):
            dieText += self.dieIDs[id]

        if len(dieMod) and dieMod[0] not in "*/-+":
            dieMod = "+" + dieMod

        dieText += dieMod
        dieText = "[" + dieText + "]"
        self.ParsePost(dieText, 1, 1)
        self.chattxt.SetFocus()


    @debugging
    def on_chat_save(self, evt):
        f = wx.FileDialog(self, "Save Chat Buffer", ".", "",
                          "HTM* (*.htm*)|*.htm*|HTML (*.html)|*.html|HTM "
                          "(*.htm)|*.htm", wx.SAVE)

        if f.ShowModal() == wx.ID_OK:
            with open(f.GetPath(), "w") as f:
                f.write(self.ResetPage() + "</body></html>")

        f.Destroy()
        os.chdir(self.root_dir)

    @debugging
    def ResetPage(self):
        self.set_colors()
        buffertext = self.chatwnd.Header() + "\n"
        buffertext += chat_util.strip_body_tags(
            self.chatwnd.StripHeader()).replace("<br>", "<br />")\
                   .replace('</html>', '').replace("<br />", "<br />\n")\
                   .replace("\n\n", '')

        return buffertext


    @debugging
    def on_text_color(self, event):
        hexcolor = self.r_h.do_hex_color_dlg(self)
        if hexcolor != None:
            (beg,end) = self.chattxt.GetSelection()
            if beg != end:
                txt = self.chattxt.GetValue()
                txt = txt[:beg]+self.colorize(hexcolor,txt[beg:end]) +txt[end:]
                self.chattxt.SetValue(txt)
                self.chattxt.SetInsertionPointEnd()
                self.chattxt.SetFocus()
            else:
                self.update_color_button(hexcolor)
                self.mytextcolor = hexcolor
                settings.set('mytextcolor',hexcolor)
                self.set_colors()
                self.Post()


    def update_color_button(self, color):
        """
        Dynamically Generates a properly colored background for the text color
        button to maintain uniform look on buttons
        """
        w,h = self.color_button_image.GetSize()
        btn_img = wx.EmptyBitmap(w,h)
        dc = wx.MemoryDC()
        dc.SelectObject(btn_img)
        dc.SetBackground(wx.Brush(color))
        dc.Clear()
        dc.DrawBitmap(self.color_button_image,0,0,True)
        self.color_button.SetBitmapLabel(btn_img)


    @debugging
    def colorize(self, color, text):
        """
        Puts font tags of 'color' around 'text' value, and returns the string
        """
        return '<font color="' + color + '">' + text + '</font>'

    @debugging
    def on_text_format(self, event):
        id = event.GetId()
        txt = self.chattxt.GetValue()
        (beg,end) = self.chattxt.GetSelection()
        if beg != end:
            sel_txt = txt[beg:end]
        else:
            sel_txt = txt

        if id == self.boldButton.GetId():
            sel_txt = "<b>" + sel_txt + "</b>"
        elif id == self.italicButton.GetId():
            sel_txt = "<i>" + sel_txt + "</i>"
        elif id == self.underlineButton.GetId():
            sel_txt = "<u>" + sel_txt + "</u>"

        if beg != end:
            txt = txt[:beg] + sel_txt + txt[end:]
        else:
            txt = sel_txt

        self.chattxt.SetValue(txt)
        self.chattxt.SetInsertionPointEnd()
        self.chattxt.SetFocus()

    @debugging
    def lock_scroll(self, event):
        if self.lockscroll:
            self.lockscroll = False
            self.scroll_lock.SetLabel("Scroll ON")

            if len(self.storedata) != 0:
                for line in self.storedata:
                    self.chatwnd.AppendToPage(line)

            self.storedata = []
            self.scroll_down()
        else:
            self.lockscroll = True
            self.scroll_lock.SetLabel("Scroll OFF")

    @debugging
    def pop_textpop(self, event):
        """
        searchable popup text view of chatbuffer
        """
        h_buffertext = self.ResetPage()
        h_dlg = orpgScrolledMessageFrameEditor(self, h_buffertext,
                                               "Text View of Chat Window",
                                               None, (500,300))
        h_dlg.Show(True)


    #@debugging This is called several times a second, dont debug it
    def OnSize(self, event=None):
        event.Skip()
        wx.CallAfter(self.scroll_down)

    @debugging
    def scroll_down(self):
        self.Freeze()
        self.chatwnd.scroll_down()
        self.Thaw()

    """
    Message Helper Methods
    """

    @debugging
    def PurgeChat(self):
        self.set_colors()
        self.chatwnd.SetPage(self.chatwnd.Header())

    @debugging
    def system_message(self, text):
        self.send_chat_message(text,chat_msg.SYSTEM_MESSAGE)
        self.SystemPost(text)

    @debugging
    def info_message(self, text):
        self.send_chat_message(text,chat_msg.INFO_MESSAGE)
        self.InfoPost(text)

    @debugging
    def get_gms(self):
        the_gms = []

        for playerid in self.session.players:
            if len(self.session.players[playerid])>7:
                if self.session.players[playerid][7]=="GM" and\
                   self.session.group_id != '0':
                    the_gms += [playerid]

        return the_gms

    @debugging
    def GetName(self):
        self.AliasLib = open_rpg.get_component('alias')
        player = self.session.get_my_info()

        if self.AliasLib != None:
            self.AliasLib.alias = self.aliasList.GetStringSelection();

            if self.AliasLib.alias[0] != self.defaultAliasName:
                return [self.chat_display_name([self.AliasLib.alias[0],
                                                player[1], player[2]]),
                        self.AliasLib.alias[1]]

        return [self.chat_display_name(player), "Default"]

    @debugging
    def GetFilteredText(self, text):
        advregex = re.compile('\"(.*?)\"', re.I)
        self.AliasLib = open_rpg.get_component('alias')

        if self.AliasLib != None:
            self.AliasLib.filter = self.filterList.GetSelection()-1;

            for rule in self.AliasLib.filterRegEx:
                if not self.advancedFilter:
                    text = re.sub(rule[0], rule[1], text)
                else:
                    for m in advregex.finditer(text):
                        match = m.group(0)
                        newmatch = re.sub(rule[0], rule[1], match)
                        text = text.replace(match, newmatch)
        return text

    @debugging
    def emote_message(self, text):
        text = self.NormalizeParse(text)
        text = self.colorize(self.emotecolor, text)

        if self.type == MAIN_TAB and self.sendtarget == 'all':
            self.send_chat_message(text,chat_msg.EMOTE_MESSAGE)
        elif self.type == MAIN_TAB and self.sendtarget == "gm":
            msg_type = chat_msg.WHISPER_EMOTE_MESSAGE
            the_gms = self.get_gms()

            for each_gm in the_gms:
                self.send_chat_message(text,chat_msg.WHISPER_EMOTE_MESSAGE,
                                       str(each_gm))
        elif self.type == GROUP_TAB and WG_LIST.has_key(self.sendtarget):
            for pid in WG_LIST[self.sendtarget]:
                self.send_chat_message(text,chat_msg.WHISPER_EMOTE_MESSAGE,
                                       str(pid))
        elif self.type == WHISPER_TAB:
            self.send_chat_message(text,chat_msg.WHISPER_EMOTE_MESSAGE,
                                   str(self.sendtarget))
        elif self.type == NULL_TAB:
            pass

        name = self.GetName()[0]
        text = "** " + name + " " + text + " **"
        self.EmotePost(text)

    @debugging
    def whisper_to_players(self, text, player_ids):
        tabbed_whispers_p = settings.get("tabbedwhispers")

        # Heroman - apply any filtering selected
        text = self.NormalizeParse(text)
        player_names = ""

        # post to our chat before we colorize
        for m in player_ids:
            id = m.strip()

            if self.session.is_valid_id(id):
                returned_name = self.session.get_player_by_player_id(id)[0]
                player_names += returned_name
                player_names += ", "
            else:
                player_names += " Unknown!"
                player_names += ", "

        comma = ","
        comma.join(player_ids)

        if (self.sendtarget == "all"):
            msg = ["<i>whispering to", player_names, text , "</i>"]
            self.InfoPost(' '.join(msg))

        # colorize and loop, sending whisper messages to all valid clients
        text = self.colorize(self.mytextcolor, text)

        for id in player_ids:
            id = id.strip()

            if self.session.is_valid_id(id):
                self.send_chat_message(text,chat_msg.WHISPER_MESSAGE,id)
            else:
                self.InfoPost(id + " Unknown!")

    @debugging
    def send_chat_message(self, text, type=chat_msg.CHAT_MESSAGE,
                          player_id="all"):
        """
        plugin send_msg hook
        """
        send = 1
        for plugin_fname in self.activeplugins.keys():
            plugin = self.activeplugins[plugin_fname]
            text, send = plugin.send_msg(text, send)

        msg = chat_msg.chat_msg()
        msg.set_text(text)
        msg.set_type(type)
        turnedoff = False

        if settings.get("ShowIDInChat") == "1":
            turnedoff = True
            settings.set("ShowIDInChat", "0")

        playername = self.GetName()[0]

        if turnedoff:
            settings.set("ShowIDInChat", "1")

        msg.set_alias(playername)

        if send:
            self.session.send(msg.toxml(),player_id)

        del msg

    @debugging
    def post_incoming_msg(self, msg, player):
        # pull data
        type = msg.get_type()
        text = msg.get_text()
        alias = msg.get_alias()

        # who sent us the message?
        if alias:
            display_name = self.chat_display_name([alias, player[1], player[2]])
        elif player:
            display_name = self.chat_display_name(player)
        else:
            display_name = "Server Administrator"

        """
        plugin incoming_msg hook
        """
        for plugin_fname in self.activeplugins.keys():
            plugin = self.activeplugins[plugin_fname]
            text, type, name = plugin.plugin_incoming_msg(text, type,
                                                          display_name,
                                                          player)

        #image stripping for players' names
        strip_img = settings.get("Show_Images_In_Chat")
        if (strip_img == "0"):
            display_name = chat_util.strip_img_tags(display_name)


        # default sound
        recvSound = "RecvSound"

        # act on the type of messsage
        if (type == chat_msg.CHAT_MESSAGE):
            text = "<b>" + display_name + "</b>: " + text
            self.Post(text)
            self.parent.newMsg(0)
        elif type == chat_msg.WHISPER_MESSAGE or\
             type == chat_msg.WHISPER_EMOTE_MESSAGE:
            tabbed_whispers_p = settings.get("tabbedwhispers")
            displaypanel = self
            whisperingstring = " (whispering): "
            panelexists = 0
            GMWhisperTab = settings.get("GMWhisperTab")
            GroupWhisperTab = settings.get("GroupWhisperTab")
            name = '<i><b>' + display_name + '</b>: '
            text += '</i>'
            panelexists = 0
            created = 0
            recvSound = "WhisperSound"

            try:
                if GMWhisperTab == '1':
                    the_gms = self.get_gms()

                    #Check if whisper if from a GM
                    if player[2] in the_gms:
                        msg = name + ' (GM Whisper:) ' + text
                        if type == chat_msg.WHISPER_MESSAGE:
                            self.parent.GMChatPanel.Post(msg)
                        else:
                            self.parent.GMChatPanel.EmotePost("**"+ msg +"**")

                        idx = self.parent.get_tab_index(self.parent.GMChatPanel)
                        self.parent.newMsg(idx)
                        panelexists = 1

                #See if message if from someone in our groups or for a
                #whisper tab we already have
                if not panelexists and GroupWhisperTab == "1":
                    for panel in self.parent.group_tabs:
                        if WG_LIST.has_key(panel.sendtarget) and\
                           WG_LIST[panel.sendtarget].has_key(int(player[2])):
                            msg = name + text
                            if type == chat_msg.WHISPER_MESSAGE:
                                panel.Post(msg)
                            else:
                                panel.EmotePost("**" + msg + "**")

                            idx = self.parent.get_tab_index(panel)
                            self.parent.newMsg(idx)
                            panelexists = 1
                            break

                if not panelexists and tabbed_whispers_p == "1":
                    for panel in self.parent.whisper_tabs:
                        #check for whisper tabs as well, to save the
                        #number of loops
                        if panel.sendtarget == player[2]:
                            msg = name + whisperingstring + text
                            if type == chat_msg.WHISPER_MESSAGE:
                                panel.Post(msg)
                            else:
                                panel.EmotePost("**" + msg + "**")

                            idx = self.parent.get_tab_index(panel)
                            self.parent.newMsg(idx)
                            panelexists = 1
                            break

                #We did not fint the tab
                if not panelexists:
                    #If we get here the tab was not found
                    if GroupWhisperTab == "1":
                        for group in WG_LIST.keys():
                            #Check if this group has the player in it
                            if WG_LIST[group].has_key(int(player[2])):
                                #Yup, post message. Player may be in more
                                #then 1 group so continue as well
                                panel = self.parent.create_group_tab(group)
                                msg = name + text
                                if type == chat_msg.WHISPER_MESSAGE:
                                    wx.CallAfter(panel.Post, msg)
                                else:
                                    wx.CallAfter(panel.EmotePost,
                                                 "**" + msg + "**")

                                created = 1

                    #Check to see if we should create a whisper tab
                    if not created and tabbed_whispers_p == "1":
                        panel = self.parent.create_whisper_tab(player[2])
                        msg = name + whisperingstring + text
                        if type == chat_msg.WHISPER_MESSAGE:
                            wx.CallAfter(panel.Post, msg)
                        else:
                            wx.CallAfter(panel.EmotePost, "**" + msg + "**")
                        created = 1

                    #Final check
                    if not created:
                        #No tabs to create, just send the message to
                        #the main chat tab
                        msg = name + whisperingstring + text
                        if type == chat_msg.WHISPER_MESSAGE:
                            self.parent.MainChatPanel.Post(msg)
                        else:
                            self.parent.MainChatPanel.EmotePost("**"+msg+"**")

                        self.parent.newMsg(0)

            except Exception, e:
                logger.exception(traceback.format_exc())

        elif (type == chat_msg.EMOTE_MESSAGE):
            text = "** " + display_name + " " + text + " **"
            self.EmotePost(text)
            self.parent.newMsg(0)
        elif (type == chat_msg.INFO_MESSAGE):
            text = "<b>" + display_name + "</b>: " + text
            self.InfoPost(text)
            self.parent.newMsg(0)
        elif (type == chat_msg.SYSTEM_MESSAGE):
            text = "<b>" + display_name + "</b>: " + text
            self.SystemPost(text)
            self.parent.newMsg(0)

        # playe sound
        sound_file = settings.get(recvSound)
        if sound_file != '':
            self.sound_player.play(sound_file)

    """
    Chat Message Posting helpers
    """

    @debugging
    def InfoPost(self, s):
        self.Post(self.colorize(self.infocolor, s))

    @debugging
    def SystemPost(self, s):
        self.Post(self.colorize(self.syscolor, s))

    @debugging
    def EmotePost(self, s):
        self.Post(self.colorize(self.emotecolor, s))

    @debugging
    def Post(self, s="", send=False, myself=False):
        strip_p = settings.get("striphtml")
        strip_img = settings.get("Show_Images_In_Chat")

        if (strip_p == "1"):
            s = chat_util.strip_html(s)

        if (strip_img == "0"):
            s = chat_util.strip_img_tags(s)

        s = chat_util.simple_html_repair(s)
        s = chat_util.strip_script_tags(s)
        s = chat_util.strip_li_tags(s)
        s = chat_util.strip_body_tags(s)
        s = chat_util.strip_misalignment_tags(s)
        aliasInfo = self.GetName()
        display_name = aliasInfo[0]

        if aliasInfo[1] != 'Default':
            defaultcolor = settings.get("mytextcolor")
            settings.set("mytextcolor", aliasInfo[1])
            self.set_colors()
        newline = ''

        """
        plugin post_msg hook
        """
        if not myself and not send:
            for plugin_fname in self.activeplugins.keys():
                plugin = self.activeplugins[plugin_fname]
                s = plugin.post_msg(s, myself)

        if myself:
            name = "<b>" + display_name + "</b>: "
            s = self.colorize(self.mytextcolor, s)
        else:
            name = ""

        if aliasInfo[1] != 'Default':
            settings.set("mytextcolor", defaultcolor)
            self.set_colors()

        lineHasText = 1
        try:
            lineHasText = chat_util.strip_html(s).replace("&nbsp;", "")\
                        .replace(" ", "").strip()!=""
        except:
            lineHasText = 1

        if lineHasText:
            if myself:
                s2 = s
                """
                plugin post_msg hook
                """
                for plugin_fname in self.activeplugins.keys():
                    plugin = self.activeplugins[plugin_fname]
                    s2 = plugin.post_msg(s2, myself)

                if s2 != "":
                    #Italici the messages from tabbed whispers
                    if self.type == WHISPER_TAB or self.type == GROUP_TAB or\
                       self.sendtarget == 'gm':
                        s2 = s2 + '</i>'
                        name = '<i>' + name

                        if self.type == WHISPER_TAB:
                            name += " (whispering): "
                        elif self.type == GROUP_TAB:
                            name += settings.get("gwtext") + ' '
                        elif self.sendtarget == 'gm':
                            name += " (whispering to GM) "

                    newline = ['<div class="post">',
                               self.TimeIndexString(),
                               name,
                               s2,
                               '</div>']
                    log(name + s2)
            else:
                newline = ['<div class="post">',
                           self.TimeIndexString(),
                           name,
                           s,
                           '</div>']
                log(name + s)
        else:
            send = False

        newline = chat_util.strip_unicode(''.join(newline))

        if self.lockscroll == 0:
            self.chatwnd.AppendToPage(newline)
            self.scroll_down()
        else:
            self.storedata.append(newline)

        if send:
            if self.type == MAIN_TAB and self.sendtarget == 'all':
                self.send_chat_message(s)
            elif self.type == MAIN_TAB and self.sendtarget == "gm":
                the_gms = self.get_gms()
                self.whisper_to_players(s, the_gms)
            elif self.type == GROUP_TAB and WG_LIST.has_key(self.sendtarget):
                members = []
                for pid in WG_LIST[self.sendtarget]:
                    members.append(str(WG_LIST[self.sendtarget][pid]))
                self.whisper_to_players(settings.get("gwtext") + s, members)
            elif self.type == WHISPER_TAB:
                self.whisper_to_players(s, [self.sendtarget])
            elif self.type == NULL_TAB:
                pass
            else:
                self.InfoPost("Failed to send message, unknown send type "
                              "for this tab")

        self.parsed=0

    @debugging
    def TimeIndexString(self):
        try:
            mtime = ""
            if settings.get('Chat_Time_Indexing') == "0":
                pass
            elif settings.get('Chat_Time_Indexing') == "1":
                mtime = time.strftime("[%I:%M:%S] ", time.localtime())

            return mtime
        except Exception, e:
            logger.exception(traceback.format_exc())
            return "[ERROR]"

    @debugging
    def ParsePost(self, s, send=False, myself=False):
        s = self.NormalizeParse(s)
        self.set_colors()
        self.Post(s,send,myself)

    @debugging
    def NormalizeParse(self, s):
        """
        plugin pre_parse hook
        """
        for plugin_fname in self.activeplugins.keys():
            plugin = self.activeplugins[plugin_fname]
            s = plugin.pre_parse(s)

        if self.parsed == 0:
            s = self.ParseNode(s)
            s = self.ParseDice(s)
            s = self.ParseFilter(s)
            self.parsed = 1

        return s

    @debugging
    def ParseFilter(self, s):
        s = self.GetFilteredText(s)
        return s

    @debugging
    def ParseNode(self, s):
        """
        Parses player input for embedded nodes rolls
        """
        cur_loc = 0
        reg = re.compile("(!@([a-zA-Z0-9 _\-\./]+(::[a-zA-Z0-9 _\-\./]+)*)@!)")
        matches = reg.findall(s)

        for i in xrange(0,len(matches)):
            newstr = self.ParseNode(self.resolve_nodes(matches[i][1]))
            s = s.replace(matches[i][0], newstr, 1)

        return s

    @debugging
    def ParseDice(self, s):
        """
        Parses player input for embedded dice rolls
        """
        reg = re.compile("\[([^]]*?)\]")
        matches = reg.findall(s)
        for i in xrange(0,len(matches)):
            newstr = self.PraseUnknowns(matches[i])
            qmode = 0
            newstr1 = newstr
            if newstr[0].lower() == 'q':
                newstr = newstr[1:]
                qmode = 1
            try:
                newstr = self.DiceManager.proccessRoll(newstr)
            except:
                pass
            if qmode == 1:
                off_roll = ["<!-- Official Roll [",
                            newstr1,
                            "] => ",
                            newstr,
                            "-->",
                            newstr]
                s = s.replace("[" + matches[i] + "]", ''.join(off_roll), 1)
            else:
                off_roll = ["[",
                            newstr1,
                            "<!-- Official Roll -->] => ",
                            newstr]
                s = s.replace("[" + matches[i] + "]", ''.join(off_roll), 1)
        return s

    @debugging
    def PraseUnknowns(self, s):
        """
        Uses a tuple. Usage: ?Label}dY. If no Label is assigned then use ?}DY
        """
        newstr = "0"
        reg = re.compile("(\?\{*)([a-zA-Z ]*)(\}*)")
        matches = reg.findall(s)

        for i in xrange(0,len(matches)):
            lb = "Replace '?' with: "
            if len(matches[i][0]):
                lb = matches[i][1] + "?: "

            dlg = wx.TextEntryDialog(self, lb, "Missing Value?")
            dlg.SetValue('')

            if matches[i][0] != '':
                dlg.SetTitle("Enter Value for " + matches[i][1])

            if dlg.ShowModal() == wx.ID_OK:
                newstr = dlg.GetValue()

            if newstr == '':
                newstr = '0'
            s = s.replace(matches[i][0], newstr, 1).replace(matches[i][1],
                                        '', 1).replace(matches[i][2], '', 1)
            dlg.Destroy()

        return s

    @debugging
    def chat_display_name(self, player):
        if settings.get("ShowIDInChat") == "0":
            display_name = player[0]
        else:
            display_name = "("+player[2]+") " + player[0]

        return display_name

    @debugging
    def get_color(self):
        data = wx.ColourData()
        data.SetChooseFull(True)
        dlg = wx.ColourDialog(self, data)
        if dlg.ShowModal() == wx.ID_OK:
            data = dlg.GetColourData()
            (red,green,blue) = data.GetColour().Get()
            hexcolor = self.r_h.hexstring(red, green, blue)
            dlg.Destroy()
            return hexcolor
        else:
            dlg.Destroy()
            return None

    @debugging
    def replace_quotes(self, s):
        in_tag = 0
        i = 0
        rs = s[:]

        for c in s:
            if c == "<":
                in_tag += 1
            elif c == ">":
                if in_tag:
                    in_tag -= 1
            elif c == '"':
                if in_tag:
                    rs = rs[:i] + "'" + rs[i+1:]
            i += 1

        return rs

    @debugging
    def resolve_loop(self, dom, nodeName, doLoop = False):
        for node in dom:
            if node._get_tagName() != 'nodehandler':
                continue

            if doLoop and node.getAttribute('class') != 'textctrl_handler' and\
               node.hasChildNodes():
                (found, node) = self.resolve_loop(node.getChildren(),
                                                  nodeName, doLoop)
                if not found:
                    continue

            if node.getAttribute('name') != nodeName:
                    continue

            foundNode = node

            return (True, foundNode)

        return (False, '')

    @debugging
    def resolve_nodes(self, s):
        value = ""
        node_path_list = s.split("::")
        gametree = open_rpg.get_component('tree')
        dom = gametree.master_dom.getChildren()

        for nodeName in node_path_list:
            (found, node) = self.resolve_loop(dom, nodeName)
            if not found:
                break
            dom = node.getChildren()

        if not found:
            dom = gametree.master_dom.getChildren()
            loop = False
            if len(node_path_list) == 1:
                loop = True

            for nodeName in node_path_list:
                (found, node) = self.resolve_loop(dom, nodeName, loop)
                if not found:
                    break

                dom = node.getChildren()
                loop = True

        if found:
            text = node.getElementsByTagName('text')
            node = text[0]._get_firstChild()
            value = node._get_nodeValue()
        else:
            value = s

        return value
